feat(expert): plan mode — propose a plan before acting (#408, #409)#7635
Conversation
…crash Add an ErrorBoundary and wrap each answer item in it, so a failure in one section degrades only that section instead of blanking the whole message. Also guard the optional streamable chain in StandardResourceCard that could throw on a null value and take down the message.
) The Expert can ask 1-4 clarifying questions in a single turn, each rendered as its own single- or multi-select option card; all answers are collected before the turn is submitted. Answered cards can be edited and resubmitted, and a card from a past turn is disabled once a newer message arrives. Adds a follow-up-questions cadence setting (all at once vs one at a time) in the composer settings menu, shipped to the agent via the expert context.
Add an always-visible Plan mode toggle to the composer. When enabled, the Expert proposes a plan instead of making changes, rendered as a plan card with Approve, Edit, Request changes and Reject actions: - Approve exits plan mode and proceeds with the plan. - Edit loads the plan markdown into the composer for direct editing. - Request changes focuses an empty composer to describe a change in words. - Reject abandons the plan. The plan card renders its markdown through RichContent (passing the message and answer uuids it requires), and reuses the composer's pending-input and auto-grow behaviour. Plan mode and the approval signal are shipped to the agent via the expert context.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #7635 +/- ##
=======================================
Coverage 75.35% 75.35%
=======================================
Files 425 425
Lines 22487 22487
Branches 5930 5930
=======================================
+ Hits 16944 16945 +1
+ Misses 5543 5542 -1
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Plan mode is only meaningful inside the instance/device editor for now, so gate the composer toggle on immersive mode and force the persisted planMode off whenever the user is outside immersive (including on load), preventing a stale value from being sent in non-immersive contexts.
- Guard the optional streamable chain in FlowResourceCard directly instead of relying on a render boundary to mask the throw. Reduce ErrorBoundary to a single last-resort backstop per answer item in AiMessage; drop the per-section boundary wrappers in AnswerWrapper. - Rewrite QuestionsList on top of the existing ff-radio-group (single-select) and ff-checkbox (multi-select) components so options look like standard, clickable form controls and stay consistent with the rest of the app. - Replace the imperative growComposerToContent DOM measuring with CSS field-sizing on the textarea; drop the manual reflows and the auto-grown flag. The composer auto-sizes to content and pins to an explicit height only after a drag-resize.
Per review: the local-catch pattern was the only one in the frontend; the rest of the app leans on the global app.config.errorHandler. The real throws are now guarded at their source (the optional streamable chains in the resource cards), so the boundary was redundant. Remove it entirely and let genuinely unexpected render errors surface through the global handler like everywhere else.
Replace the composer kebab menu with a settings gear that opens an ff-dialog. The follow-up-questions cadence control now lives in the dialog as an ff-radio-group, with a FormHeading per section so the panel can grow as more settings are added.
…n-mode # Conflicts: # frontend/src/components/expert/components/ExpertChatInput.vue # frontend/src/components/expert/components/messages/components/AnswerWrapper.vue
handleMessageResponse lived in the composer's send handler, so a reply to a query sent from the questions card was fetched but never rendered over HTTP (without comms-beta the reply only renders via the MQTT push handler). Fold the response handling into handleQuery so every entry point (composer and the question/plan cards) renders the reply without re-implementing it.
…n-mode # Conflicts: # frontend/src/components/expert/components/ExpertChatInput.vue # frontend/src/stores/product-expert.js
There was a problem hiding this comment.
Hey flagged a couple things. I wasn't able to test this since we don't have a staging env for non mainline PRs. Lmk when this is pointed at main and I will test. nvm I am wrong, will test now as well and update.
One general note - there are a lot of code comments here, and Serban got on me for this exact thing too so you're not alone. When they get this verbose they kinda make the code harder to read. My rule of thumb is if you can figure it out from reading the code, don't comment it. Might be worth dropping into a CLAUDE.md.
…mode # Conflicts: # frontend/src/components/expert/components/ExpertChatInput.vue # frontend/src/components/expert/components/messages/components/AnswerWrapper.vue # frontend/src/stores/context.js # frontend/src/stores/product-expert.js
- Remove the unused contextOverrides param threaded through handleQuery/sendQuery. - Stop persisting planMode so approving a plan (which turns it off) no longer wipes the user's preference; it defaults off per session. - Replace the planChangeRequest/composerReset counters with a single composerCommand consumed and cleared like pendingInput, collapsing two composer watchers into one. - Give the composer textarea min-height: 0 so a long plan loaded via Edit manually scrolls instead of overflowing the chat. - Add a size="small" variant to the shared ff-toggle-switch and drop the fragile :deep sizing overrides. The prop defaults to "normal" (an inert class) so existing consumers are unchanged. - Trim verbose comments across the plan-mode files.
Drop the immersive-only gate on the plan mode toggle and the watcher that forced it off when leaving the editor, so plan mode is offered in both the FlowFuse app and the instance/device editor.
Fair, and noted. Did a pass to cut the comments back to just the non-obvious stuff and left the code to speak for itself where it can. |
The min-height: 0 needed for the composer to scroll instead of grow was only on the textarea, not its flex parent. Add it to .input-wrapper so the whole flex chain can shrink and a long plan loaded via Edit scrolls within the capped composer.
|
Pushed the review fixes:
One change beyond the review: I removed the logic that limited plan mode to the immersive editor, so it's now available across the app rather than only inside an instance/device editor. Plan mode now adapts to where the user is: it uses the Here is a short recording of the plan-mode flow in action: Screen.Recording.2026-07-01.at.12.40.43.mov@n-lark would you mind giving it a last look? |
|
PR was approved but we will need to wait for agent deployment to production before merging, otherwise users will see plan mode but will not be working until agent is updated |
…mode # Conflicts: # frontend/src/stores/context.js

Summary
Frontend for Expert plan mode (FlowFuse/product#408, FlowFuse/product#409): the Expert proposes a plan before making any changes, and acts only once the user approves it.
Stacking
This PR was stacked on #7556 (clarifying questions), which has now merged. It now targets
maindirectly and the diff shown here is plan-mode only.Testing
npm run lint,npm run buildandnpm run test:unit:frontend(570 tests) all pass.Closes: